﻿namespace wToggle.Platform
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;

    using Windows.UI.Xaml.Automation;

    using wToggle.Platform.Data;
    using wToggle.Platform.Interfaces;

    using XPlatUtils;

    public class TimeEntryProvider : ITimeEntryProvider
    {
        public async Task<TimeEntry> Create(TimeEntry entry, bool withLock = true)
        {
            if (entry == null)
            {
                throw new ArgumentNullException("entry");
            }

            if (Toggly.HasInternetAccess())
            {
                entry.CreatedWith = "Toggly 1.0";
                var auth = ServiceContainer.Resolve<ITogglAuth>();

                using (var client = auth.GetPreauthenticatedClient())
                {
                    var result =
                        await
                        client.PostJsonAsync<ResponseData<TimeEntry>, TimeEntryRequest>(
                            "time_entries",
                            new TimeEntryRequest { Data = entry });

                    if (result == null)
                    {
                        return null;
                    }

                    entry.Id = result.Data.Id;
                    await CacheLayer.StoreEntity(result.Data, withLock);

                    return result.Data;
                }
            }
            else
            {
                await CacheLayer.StoreEntryWrite(entry, WriteType.Create, withLock);
                return entry;
            }
        }

        public async Task<ICollection<TimeEntry>> GetEntries(DateTime startUtc, DateTime endUtc, bool expandProjects = false)
        {
            var cached = CacheLayer.LoadEntities<TimeEntry>();

            if (cached != null)
            {
                var timeEntries =
                    cached.Where(
                        te =>
                        te.Start.HasValue && te.Start.Value.ToUniversalTime() >= startUtc && te.End.HasValue
                        && te.End.Value.ToUniversalTime() <= endUtc).OrderByDescending(t => t.Start).ToList();

                foreach (var entry in timeEntries)
                {
                    await entry.ExpandProject();
                }

                if (timeEntries.Any())
                {
                    return timeEntries;
                }
            }

            var auth = ServiceContainer.Resolve<ITogglAuth>();

            using (var client = auth.GetPreauthenticatedClient())
            {
                var result =
                    await
                    client.GetJsonAsync<List<TimeEntry>>(
                        string.Format("time_entries?start_date={0}&end_date={1}", startUtc.ToString("o"), endUtc.ToString("o")));

                if (result == null)
                {
                    return new List<TimeEntry>();
                }

                if (expandProjects)
                {
                    foreach (var entry in result)
                    {
                        await entry.ExpandProject();
                    }
                }

                await CacheLayer.StoreEntities(result);

                return result.OrderByDescending(t => t.Start).ToList();
            }
        }

        public async Task<bool> Delete(ulong id, bool withLock = true)
        {
            if (id <= 0)
            {
                throw new ArgumentOutOfRangeException("id", "Time entry ID out of range");
            }

            if (Toggly.HasInternetAccess())
            {
                var auth = ServiceContainer.Resolve<ITogglAuth>();

                using (var client = auth.GetPreauthenticatedClient())
                {
                    var result = await client.DeleteAsync("time_entries/" + id);

                    try
                    {
                        result.EnsureSuccessStatusCode();

                        await CacheLayer.RemoveEntity<TimeEntry>(id, withLock);

                        return true;
                    }
                    catch (HttpRequestException e)
                    {
                        return false;
                    }
                }
            }
            else
            {
                var existing = CacheLayer.LoadEntity<TimeEntry>(id);

                if (existing == null)
                {
                    return true;
                }

                await CacheLayer.StoreEntryWrite(existing, WriteType.Delete, withLock);
                return true;
            }
        }

        public async Task<TimeEntry> GetEntry(ulong id)
        {
            if (id <= 0)
            {
                throw new ArgumentOutOfRangeException("id", "Time entry ID out of range");
            }

            var cached = CacheLayer.LoadEntity<TimeEntry>(id);

            if (cached != null)
            {
                return cached;
            }

            var auth = ServiceContainer.Resolve<ITogglAuth>();

            using (var client = auth.GetPreauthenticatedClient())
            {
                var result = await client.GetJsonAsync<ResponseData<TimeEntry>>("time_entries/" + id);

                if (result == null)
                {
                    return null;
                }

                await CacheLayer.StoreEntity(result.Data);

                return result.Data;
            }
        }

        public async Task<ICollection<ReportTimeEntry>> FilterEntries(
            ulong workspaceId,
            DateTime from,
            DateTime to,
            ICollection<ulong> clientIds = null,
            ICollection<ulong> projectIds = null,
            ICollection<ulong> tagIds = null)
        {
            if (workspaceId <= 0)
            {
                throw new ArgumentOutOfRangeException("workspaceId", "Invalid workspace ID specified");
            }

            var data = CacheLayer.LoadEntity<UserData>(0);

            if (data == null)
            {
                throw new InvalidOperationException("Unable to read login credentials");
            }

            if (Toggly.HasInternetAccess())
            {
                var result = new List<ReportTimeEntry>();
                uint totalCount;
                var page = 1U;

                do
                {
                    var r = await GetPage(workspaceId, @from, to, clientIds, projectIds, tagIds, data, page++);

                    if (r == null)
                    {
                        return result;
                    }

                    totalCount = r.TotalCount;
                    result.AddRange(r.Data);
                }
                while (totalCount > result.Count);

                return result;
            }
            else
            {
                return new List<ReportTimeEntry>();
            }
        }

        public async Task<byte[]> PdfReport(
            ulong workspaceId,
            DateTime @from,
            DateTime to,
            ICollection<ulong> clientIds = null,
            ICollection<ulong> projectIds = null,
            ICollection<ulong> tagIds = null)
        {
            if (workspaceId <= 0)
            {
                throw new ArgumentOutOfRangeException("workspaceId", "Invalid workspace ID specified");
            }

            var data = CacheLayer.LoadEntity<UserData>(0);

            if (data == null)
            {
                throw new InvalidOperationException("Unable to read login credentials");
            }

            if (Toggly.HasInternetAccess())
            {
                var dictionary = new Dictionary<string, object>
                                     {
                                         { "user_agent", "Toggly" },
                                         { "workspace_id", workspaceId },
                                         { "since", from.ToString("yyyy-MM-dd") },
                                         { "until", to.ToString("yyyy-MM-dd") }
                                     };

                if (clientIds != null && clientIds.Any())
                {
                    dictionary.Add("client_ids", string.Join(",", clientIds));
                }

                if (projectIds != null && projectIds.Any())
                {
                    dictionary.Add("project_ids", string.Join(",", projectIds));
                }

                if (tagIds != null && tagIds.Any())
                {
                    dictionary.Add("tag_ids", string.Join(",", tagIds));
                }

                var urlParams = string.Join("&", dictionary.Select(kvp => kvp.Key + "=" + kvp.Value));

                using (var client = new HttpClient())
                {
                    var byteArray = string.Format("{0}:{1}", data.ApiToken, "api_token").ToASCII();
                    var header = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
                    client.DefaultRequestHeaders.Authorization = header;
                    client.BaseAddress = null;
                    var result = await client.GetAsync("https://toggl.com/reports/api/v2/details.pdf?" + urlParams);

                    try
                    {
                        result.EnsureSuccessStatusCode();
                        return await result.Content.ReadAsByteArrayAsync();
                    }
                    catch (Exception)
                    {
                        return null;
                    }
                }
            }
            else
            {
                return null;
            }
        }

        private static async Task<DetailReportResponse> GetPage(
            ulong workspaceId,
            DateTime @from,
            DateTime to,
            ICollection<ulong> clientIds,
            ICollection<ulong> projectIds,
            ICollection<ulong> tagIds,
            UserData data,
            uint page = 1)
        {
            var dictionary = new Dictionary<string, object>
                                 {
                                     { "user_agent", "Toggly" },
                                     { "workspace_id", workspaceId },
                                     { "since", @from.ToString("o") },
                                     { "until", to.ToString("o") },
                                     { "page", page }
                                 };

            if (clientIds != null && clientIds.Any())
            {
                dictionary.Add("client_ids", string.Join(",", clientIds));
            }

            if (projectIds != null && projectIds.Any())
            {
                dictionary.Add("project_ids", string.Join(",", projectIds));
            }

            if (tagIds != null && tagIds.Any())
            {
                dictionary.Add("tag_ids", string.Join(",", tagIds));
            }

            var urlParams = string.Join("&", dictionary.Select(kvp => kvp.Key + "=" + kvp.Value));

            using (var client = new HttpClient())
            {
                var byteArray = string.Format("{0}:{1}", data.ApiToken, "api_token").ToASCII();
                var header = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
                client.DefaultRequestHeaders.Authorization = header;
                client.BaseAddress = null;
                var result =
                    await client.GetJsonAsync<DetailReportResponse>("https://toggl.com/reports/api/v2/details?" + urlParams);

                return result;
            }
        }

        public async Task<TimeEntry> Update(TimeEntry entry, bool withLock = true)
        {
            if (entry == null)
            {
                throw new ArgumentNullException("entry");
            }

            if (entry.Id <= 0)
            {
                throw new ArgumentException("Time entry ID out of range");
            }

            if (Toggly.HasInternetAccess())
            {
                var auth = ServiceContainer.Resolve<ITogglAuth>();

                using (var client = auth.GetPreauthenticatedClient())
                {
                    var result =
                        await
                        client.PutJsonAsync<ResponseData<TimeEntry>, TimeEntryRequest>(
                            "time_entries/" + entry.Id,
                            new TimeEntryRequest { Data = entry });

                    if (result == null)
                    {
                        return null;
                    }

                    await CacheLayer.StoreEntity(result.Data, withLock);

                    return result.Data;
                }
            }
            else
            {
                await CacheLayer.StoreEntryWrite(entry, WriteType.Update, withLock);
                return entry;
            }
        }
    }
}
